#include "file_watcher.h"
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/inotify.h>

IN_MOVE_SELF

file_watcher* file_watcher::create(uv_loop_t *loop) {
    int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
    if (fd < 0) {
        //sk_fatal("")
        printf("inotify init error<%s>\n", strerror(errno));
        return nullptr;
    }

    return new(std::nothrow) file_watcher(fd, loop);
}

file_watcher::file_watcher(int inotify_fd, uv_loop_t *loop) :
    inotify_fd_(inotify_fd), loop_(loop) {
    memset(&poll_, 0x00, sizeof(poll_));
    poll_.data = this;
}

file_watcher::~file_watcher() {
    for (const auto& it : wd2file_) {
        int ret = inotify_rm_watch(inotify_fd_, it.first);
        if (ret != 0)
            printf("intify rm watch error<%s>\n", strerror(errno));
    }

    wd2file_.clear();
    file2wd_.clear();

    if (inotify_fd_ != -1) {
        close(inotify_fd_);
        inotify_fd_ = -1;
    }
}

int file_watcher::watch(const std::string& file) {
    printf("watch %s\n", file.c_str());

    auto it = file2wd_.find(file);
    if (it != file2wd_.end()) return -EEXIST;

    int wd = inotify_add_watch(inotify_fd_, file.c_str(), IN_ALL_EVENTS);
    if (wd < 0) {
        printf("inotify add watch file<%s> error<%s>\n", file.c_str(), strerror(errno));
        return -1;
    }

    file2wd_[file] = wd;
    wd2file_[wd] = std::move(file);

    return 0;
}

void file_watcher::unwatch(const std::string& file) {
    auto it = file2wd_.find(file);
    if (it == file2wd_.end()) return;

    int ret = inotify_rm_watch(inotify_fd_, it->second);
    if (ret != 0) {
        printf("rm inotify watch error<%s>.\n", strerror(errno));
        return;
    }

    wd2file_.erase(it->second);
    file2wd_.erase(it);
}

bool file_watcher::has_watch(const std::string& file) const {
    auto it = file2wd_.find(file);
    bool found = it != file2wd_.end();
    if (found)
        assert(wd2file_.find(it->second) != wd2file_.end());

    return found;
}

int file_watcher::start() {
    int ret = uv_poll_init(loop_, &poll_, inotify_fd_);
    if (ret != 0) {
        printf("poll init error<%s>.\n", uv_err_name(ret));
        return ret;
    }

    ret = uv_poll_start(&poll_, UV_READABLE | UV_WRITABLE | UV_DISCONNECT, on_inotify_event);
    if (ret != 0) {
        printf("poll start error<%s>.\n", uv_err_name(ret));
        return ret;
    }

    return 0;
}

int file_watcher::stop() {
    int ret = uv_poll_stop(&poll_);
    if (ret != 0) {
        printf("uv_poll_stop<%s>.\n", uv_err_name(ret));
        return ret;
    }

    return 0;
}

void file_watcher::on_inotify_event(uv_poll_t *handle, int status, int event) {
    printf("%d %d\n", status, event);
    file_watcher * fw = (file_watcher *)handle->data;
    assert(&fw->poll_ == handle);

    if (status != 0) {
        printf("on_inotify_event error<%s>.\n", uv_err_name(status));
        return;
    }

    if (event & UV_WRITABLE) printf("writable event\n");
    if (event & UV_DISCONNECT) printf("disconnect event\n");

    if (event & UV_READABLE) return fw->on_inotify_read();
}

void file_watcher::on_inotify_read() {
    char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));

    const struct inotify_event *event = nullptr;

    ssize_t len = 0;
    while(true) {
        do {
            len = read(inotify_fd_, buf, sizeof(buf));
        } while (len == -1 && errno == EINTR);

        if (len == -1) {
            if (errno != EAGAIN && errno != EWOULDBLOCK)
                printf("inotify read error<%s>.\n", strerror(errno));

            break;
        }

        if (len == 0) break;

        printf("read size:%ld\n", len);

        for (char *ptr = buf; ptr < buf + len; ptr += sizeof(inotify_event) + event->len) {
            event = reinterpret_cast<const struct inotify_event *>(ptr);
            printf("%d %s\n", event->mask, event->name);
            if (event->mask & IN_IGNORED) continue;

            auto it = wd2file_.find(event->wd);
            if (it == wd2file_.end()) {
                printf("file watch<%d> not found.\n", event->wd);
                continue;
            }

            printf("OPEN<%d>, CLOSE<%d>, CHANGE<%d>, DELETE<%d>, file<%s>.\n",
                   event->mask & IN_OPEN ? 1 : 0, event->mask & IN_CLOSE ? 1 : 0,
                   event->mask & IN_MODIFY ? 1 : 0, event->mask & IN_DELETE_SELF ? 1 : 0,
                   it->second.c_str());

            if ((event->mask & IN_OPEN) && fn_on_file_open_)
                fn_on_file_open_(it->second);

            if ((event->mask & IN_CLOSE) && fn_on_file_close_)
                fn_on_file_close_(it->second);

            if ((event->mask & IN_MODIFY) && fn_on_file_change_)
                fn_on_file_change_(it->second);

            if (event->mask & IN_MOVE)
                printf("%s moved\n", it->second.c_str());

            if (event->mask & IN_ACCESS)
                printf("%s accessed\n", it->second.c_str());

            if ((event->mask & IN_DELETE_SELF) && fn_on_file_delete_)
                fn_on_file_delete_(it->second);
        }
    }
}
